 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.ide.dialogs;

 import java.util.TreeSet ;

 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.IWorkspaceDescription;
 import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jface.preference.FieldEditor;
 import org.eclipse.jface.preference.IntegerFieldEditor;
 import org.eclipse.jface.preference.PreferencePage;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jface.viewers.ILabelProvider;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.List;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPreferencePage;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.actions.GlobalBuildAction;
 import org.eclipse.ui.dialogs.ListSelectionDialog;
 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
 import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
 import org.eclipse.ui.internal.util.PrefUtil;

 /**
  * Page used to determine what order projects will be built in
  * by the workspace.
  */
 public class BuildOrderPreferencePage extends PreferencePage implements
         IWorkbenchPreferencePage {

     private IWorkbench workbench;

     private Button defaultOrderButton;

     private Label buildLabel;

     private List buildList;

     private Composite buttonComposite;

     private IntegerFieldEditor maxItersField;

     private String [] defaultBuildOrder;

     private String [] customBuildOrder;

     //Boolean to indicate if we have looked it up
 private boolean notCheckedBuildOrder = true;

     private final String UP_LABEL = IDEWorkbenchMessages.BuildOrderPreference_up;

     private final String DOWN_LABEL = IDEWorkbenchMessages.BuildOrderPreference_down;

     private final String ADD_LABEL = IDEWorkbenchMessages.BuildOrderPreference_add;

     private final String REMOVE_LABEL = IDEWorkbenchMessages.BuildOrderPreference_remove;

     private final String PROJECT_SELECTION_MESSAGE = IDEWorkbenchMessages.BuildOrderPreference_selectOtherProjects;

     private final String DEFAULTS_LABEL = IDEWorkbenchMessages.BuildOrderPreference_useDefaults;

     private final String LIST_LABEL = IDEWorkbenchMessages.BuildOrderPreference_projectBuildOrder;

     private final String NOTE_LABEL = IDEWorkbenchMessages.Preference_note;

     private final String REMOVE_MESSAGE = IDEWorkbenchMessages.BuildOrderPreference_removeNote;

     // whether or not the use defaults option was selected when Apply (or OK) was last pressed
 // (or when the preference page was opened). This represents the most recent applied state.
 private boolean defaultOrderInitiallySelected;

     private IPropertyChangeListener validityChangeListener = new IPropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent event) {
             if (event.getProperty().equals(FieldEditor.IS_VALID)) {
                 updateValidState();
             }
         }
     };

     /**
      * Add another project to the list at the end.
      */
     private void addProject() {

         String [] currentItems = this.buildList.getItems();

         IProject[] allProjects = getWorkspace().getRoot().getProjects();

         ILabelProvider labelProvider = new LabelProvider() {
             public String getText(Object element) {
                 return (String ) element;
             }
         };

         SimpleListContentProvider contentsProvider = new SimpleListContentProvider();
         contentsProvider
                 .setElements(sortedDifference(allProjects, currentItems));

         ListSelectionDialog dialog = new ListSelectionDialog(this.getShell(),
                 this, contentsProvider, labelProvider,
                 PROJECT_SELECTION_MESSAGE);

         if (dialog.open() != Window.OK) {
             return;
         }

         Object [] result = dialog.getResult();

         int currentItemsLength = currentItems.length;
         int resultLength = result.length;
         String [] newItems = new String [currentItemsLength + resultLength];

         System.arraycopy(currentItems, 0, newItems, 0, currentItemsLength);
         System
                 .arraycopy(result, 0, newItems, currentItemsLength,
                         result.length);
         this.buildList.setItems(newItems);
     }

     /**
      * Updates the valid state of the page.
      */
     private void updateValidState() {
         setValid(maxItersField.isValid());
     }

     /**
      * Create the list of build paths. If the current build order is empty make the list empty
      * and disable it.
      * @param composite - the parent to create the list in
      * @param enabled - the boolean that indcates if the list will be sensitive initially or not
      */
     private void createBuildOrderList(Composite composite, boolean enabled) {

         Font font = composite.getFont();

         this.buildLabel = new Label(composite, SWT.NONE);
         this.buildLabel.setText(LIST_LABEL);
         this.buildLabel.setEnabled(enabled);
         GridData gridData = new GridData();
         gridData.horizontalAlignment = GridData.FILL;
         gridData.horizontalSpan = 2;
         this.buildLabel.setLayoutData(gridData);
         this.buildLabel.setFont(font);

         this.buildList = new List(composite, SWT.BORDER | SWT.MULTI
                 | SWT.H_SCROLL | SWT.V_SCROLL);
         this.buildList.setEnabled(enabled);
         GridData data = new GridData();
         //Set heightHint with a small value so the list size will be defined by
 //the space available in the dialog instead of resizing the dialog to
 //fit all the items in the list.
 data.heightHint = buildList.getItemHeight();
         data.verticalAlignment = GridData.FILL;
         data.horizontalAlignment = GridData.FILL;
         data.grabExcessHorizontalSpace = true;
         data.grabExcessVerticalSpace = true;
         this.buildList.setLayoutData(data);
         this.buildList.setFont(font);
     }

     /**
      * Create the widgets that are used to determine the build order.
      *
      * @param parent the parent composite
      * @return the new control
      */
     protected Control createContents(Composite parent) {

         PlatformUI.getWorkbench().getHelpSystem().setHelp(parent,
                 IIDEHelpContextIds.BUILD_ORDER_PREFERENCE_PAGE);

         Font font = parent.getFont();

         //The main composite
 Composite composite = new Composite(parent, SWT.NULL);
         GridLayout layout = new GridLayout();
         layout.numColumns = 2;
         layout.marginWidth = 0;
         layout.marginHeight = 0;
         composite.setLayout(layout);
         GridData data = new GridData();
         data.verticalAlignment = GridData.FILL;
         data.horizontalAlignment = GridData.FILL;
         composite.setLayoutData(data);
         composite.setFont(font);

         String [] buildOrder = getCurrentBuildOrder();
         boolean useDefault = (buildOrder == null);

         createDefaultPathButton(composite, useDefault);
         // List always enabled so user can scroll list.
 // Only the buttons need to be disabled.
 createBuildOrderList(composite, true);
         createListButtons(composite, !useDefault);

         Composite noteComposite = createNoteComposite(font, composite,
                 NOTE_LABEL, REMOVE_MESSAGE);
         GridData noteData = new GridData();
         noteData.horizontalSpan = 2;
         noteComposite.setLayoutData(noteData);

         createSpacer(composite);

         createMaxIterationsField(composite);

         createSpacer(composite);

         if (useDefault) {
             this.buildList.setItems(getDefaultProjectOrder());
         } else {
             this.buildList.setItems(buildOrder);
         }

         return composite;

     }

     /**
      * Adds in a spacer.
      *
      * @param composite the parent composite
      */
     private void createSpacer(Composite composite) {
         Label spacer = new Label(composite, SWT.NONE);
         GridData spacerData = new GridData();
         spacerData.horizontalSpan = 2;
         spacer.setLayoutData(spacerData);
     }

     /**
      * Create the default path button. Set it to selected based on the current workspace
      * build path.
      * @param composite org.eclipse.swt.widgets.Composite
      * @param selected - the boolean that indicates the buttons initial state
      */
     private void createDefaultPathButton(Composite composite, boolean selected) {

         defaultOrderInitiallySelected = selected;

         this.defaultOrderButton = new Button(composite, SWT.LEFT | SWT.CHECK);
         this.defaultOrderButton.setSelection(selected);
         this.defaultOrderButton.setText(DEFAULTS_LABEL);
         SelectionListener listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 defaultsButtonSelected(defaultOrderButton.getSelection());
             }
         };
         this.defaultOrderButton.addSelectionListener(listener);

         GridData gridData = new GridData();
         gridData.horizontalAlignment = GridData.FILL;
         gridData.horizontalSpan = 2;
         this.defaultOrderButton.setLayoutData(gridData);
         this.defaultOrderButton.setFont(composite.getFont());
     }

     /**
      * Create the buttons used to manipulate the list. These Add, Remove and Move Up or Down
      * the list items.
      * @param composite the parent of the buttons
      * @param enableComposite - boolean that indicates if a composite should be enabled
      */
     private void createListButtons(Composite composite, boolean enableComposite) {

         Font font = composite.getFont();

         //Create an intermeditate composite to keep the buttons in the same column
 this.buttonComposite = new Composite(composite, SWT.RIGHT);
         GridLayout layout = new GridLayout();
         layout.marginWidth = 0;
         layout.marginHeight = 0;
         this.buttonComposite.setLayout(layout);
         GridData gridData = new GridData();
         gridData.verticalAlignment = GridData.FILL;
         gridData.horizontalAlignment = GridData.FILL;
         this.buttonComposite.setLayoutData(gridData);
         this.buttonComposite.setFont(font);

         Button upButton = new Button(this.buttonComposite, SWT.CENTER
                 | SWT.PUSH);
         upButton.setText(UP_LABEL);
         upButton.setEnabled(enableComposite);
         upButton.setFont(font);
         setButtonLayoutData(upButton);

         SelectionListener listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 moveSelectionUp();
             }
         };
         upButton.addSelectionListener(listener);

         Button downButton = new Button(this.buttonComposite, SWT.CENTER
                 | SWT.PUSH);
         downButton.setText(DOWN_LABEL);
         downButton.setEnabled(enableComposite);
         listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 moveSelectionDown();
             }
         };
         downButton.addSelectionListener(listener);
         downButton.setFont(font);
         setButtonLayoutData(downButton);

         Button addButton = new Button(this.buttonComposite, SWT.CENTER
                 | SWT.PUSH);
         addButton.setText(ADD_LABEL);
         listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 addProject();
             }
         };
         addButton.addSelectionListener(listener);
         addButton.setEnabled(enableComposite);
         addButton.setFont(font);
         setButtonLayoutData(addButton);

         Button removeButton = new Button(this.buttonComposite, SWT.CENTER
                 | SWT.PUSH);
         removeButton.setText(REMOVE_LABEL);
         listener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 removeSelection();
             }
         };
         removeButton.addSelectionListener(listener);
         removeButton.setEnabled(enableComposite);
         removeButton.setFont(font);
         setButtonLayoutData(removeButton);

     }

     /**
      * Create the field for the maximum number of iterations in the presence
      * of cycles.
      */
     private void createMaxIterationsField(Composite composite) {
         Composite maxItersComposite = new Composite(composite, SWT.NONE);
         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
         maxItersComposite.setLayoutData(gd);
         maxItersComposite.setFont(composite.getFont());

         maxItersField = new IntegerFieldEditor(
                 "", IDEWorkbenchMessages.BuildOrderPreference_maxIterationsLabel, maxItersComposite) { //$NON-NLS-1$
 protected void doLoad() {
                 Text text = getTextControl();
                 if (text != null) {
                     int value = getWorkspace().getDescription()
                             .getMaxBuildIterations();
                     text.setText(Integer.toString(value));
                 }
             }

             protected void doLoadDefault() {
                 Text text = getTextControl();
                 if (text != null) {
                     int value = ResourcesPlugin.getPlugin()
                             .getPluginPreferences().getDefaultInt(
                                     ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS);
                     text.setText(Integer.toString(value));
                 }
                 valueChanged();
             }

             protected void doStore() {
                 // handled specially in performOK()
 throw new UnsupportedOperationException ();
             }
         };
         maxItersField.setValidRange(1, Integer.MAX_VALUE);
         maxItersField.setPage(this);
         maxItersField.setPreferenceStore(getPreferenceStore());
         maxItersField.setPropertyChangeListener(validityChangeListener);
         maxItersField.load();
     }

     /**
      * The defaults button has been selected - update the other widgets as required.
      * @param selected - whether or not the defaults button got selected
      */
     private void defaultsButtonSelected(boolean selected) {
         if (selected) {
             setBuildOrderWidgetsEnablement(false);
             //Cache the current value as the custom order
 customBuildOrder = buildList.getItems();
             buildList.setItems(getDefaultProjectOrder());

         } else {
             setBuildOrderWidgetsEnablement(true);
             String [] buildOrder = getCurrentBuildOrder();
             if (buildOrder == null) {
                 buildList.setItems(getDefaultProjectOrder());
             } else {
                 buildList.setItems(buildOrder);
             }
         }
     }

     /**
      * Get the project names for the current custom build
      * order stored in the workspace description.
      *
      * @return java.lang.String[] or null if there is no setting
      */
     private String [] getCurrentBuildOrder() {
         if (notCheckedBuildOrder) {
             customBuildOrder = getWorkspace().getDescription().getBuildOrder();
             notCheckedBuildOrder = false;
         }

         return customBuildOrder;
     }

     /**
      * Get the project names in the default build order
      * based on the current Workspace settings.
      *
      * @return java.lang.String[]
      */
     private String [] getDefaultProjectOrder() {
         if (defaultBuildOrder == null) {
             IWorkspace workspace = getWorkspace();
             IWorkspace.ProjectOrder projectOrder = getWorkspace()
                     .computeProjectOrder(workspace.getRoot().getProjects());
             IProject[] foundProjects = projectOrder.projects;
             defaultBuildOrder = new String [foundProjects.length];
             int foundSize = foundProjects.length;
             for (int i = 0; i < foundSize; i++) {
                 defaultBuildOrder[i] = foundProjects[i].getName();
             }
         }

         return defaultBuildOrder;
     }

     /**
      * Return the Workspace the build order is from.
      * @return org.eclipse.core.resources.IWorkspace
      */
     private IWorkspace getWorkspace() {
         return ResourcesPlugin.getWorkspace();
     }

     /**
      * Return whether or not searchElement is in testArray.
      */
     private boolean includes(String [] testArray, String searchElement) {

         for (int i = 0; i < testArray.length; i++) {
             if (searchElement.equals(testArray[i])) {
                 return true;
             }
         }
         return false;

     }

     /**
      * See IWorkbenchPreferencePage. This class does nothing with he Workbench.
      */
     public void init(IWorkbench workbench) {
         this.workbench = workbench;
         setPreferenceStore(PrefUtil.getInternalPreferenceStore());
     }

     /**
      * Move the current selection in the build list down.
      */
     private void moveSelectionDown() {

         //Only do this operation on a single selection
 if (this.buildList.getSelectionCount() == 1) {
             int currentIndex = this.buildList.getSelectionIndex();
             if (currentIndex < this.buildList.getItemCount() - 1) {
                 String elementToMove = this.buildList.getItem(currentIndex);
                 this.buildList.remove(currentIndex);
                 this.buildList.add(elementToMove, currentIndex + 1);
                 this.buildList.select(currentIndex + 1);
             }
         }
     }

     /**
      * Move the current selection in the build list up.
      */
     private void moveSelectionUp() {

         int currentIndex = this.buildList.getSelectionIndex();

         //Only do this operation on a single selection
 if (currentIndex > 0 && this.buildList.getSelectionCount() == 1) {
             String elementToMove = this.buildList.getItem(currentIndex);
             this.buildList.remove(currentIndex);
             this.buildList.add(elementToMove, currentIndex - 1);
             this.buildList.select(currentIndex - 1);
         }
     }

     /**
      * Performs special processing when this page's Defaults button has been pressed.
      * In this case change the defaultOrderButton to have it's selection set to true.
      */
     protected void performDefaults() {
         this.defaultOrderButton.setSelection(true);
         defaultsButtonSelected(true);
         maxItersField.loadDefault();
         super.performDefaults();
     }

     /**
      * OK has been pressed. If the defualt button is pressed then reset the build order to false;
      * otherwise set it to the contents of the list.
      */
     public boolean performOk() {

         String [] buildOrder = null;
         boolean useDefault = defaultOrderButton.getSelection();

         // if use defaults is turned off
 if (!useDefault) {
             buildOrder = buildList.getItems();
         }

         //Get a copy of the description from the workspace, set the build order and then
 //apply it to the workspace.
 IWorkspaceDescription description = getWorkspace().getDescription();
         description.setBuildOrder(buildOrder);
         description.setMaxBuildIterations(maxItersField.getIntValue());
         try {
             getWorkspace().setDescription(description);
         } catch (CoreException exception) {
             //failed - return false
 return false;
         }

         // Perform auto-build if use default is off (because
 // order could have changed) or if use default setting
 // was changed.
 if (!useDefault || (useDefault != defaultOrderInitiallySelected)) {
             defaultOrderInitiallySelected = useDefault;
             // If auto build is turned on, then do a global incremental
 // build on all the projects.
 if (ResourcesPlugin.getWorkspace().isAutoBuilding()) {
                 GlobalBuildAction action = new GlobalBuildAction(workbench
                         .getActiveWorkbenchWindow(),
                         IncrementalProjectBuilder.INCREMENTAL_BUILD);
                 action.doBuild();
             }
         }

         // Clear the custom build order cache
 customBuildOrder = null;

         return true;
     }

     /**
      * Remove the current selection in the build list.
      */
     private void removeSelection() {

         this.buildList.remove(this.buildList.getSelectionIndices());
     }

     /**
      * Set the widgets that select build order to be enabled or diabled.
      * @param value boolean
      */
     private void setBuildOrderWidgetsEnablement(boolean value) {

         // Only change enablement of buttons. Leave list alone
 // because you can't scroll it when disabled.
 Control[] children = this.buttonComposite.getChildren();
         for (int i = 0; i < children.length; i++) {
             children[i].setEnabled(value);
         }
     }

     /**
      * Return a sorted array of the names of the projects that are already in the currently
      * displayed names.
      * @return String[]
      * @param allProjects - all of the projects in the workspace
      * @param currentlyDisplayed - the names of the projects already being displayed
      */
     private String [] sortedDifference(IProject[] allProjects,
             String [] currentlyDisplayed) {

         TreeSet difference = new TreeSet ();

         for (int i = 0; i < allProjects.length; i++) {
             if (!includes(currentlyDisplayed, allProjects[i].getName())) {
                 difference.add(allProjects[i].getName());
             }
         }

         String [] returnValue = new String [difference.size()];
         difference.toArray(returnValue);
         return returnValue;
     }
 }

